package org.zachary.imitation.raft.storage;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zachary.imitation.raft.NodeImpl;
import org.zachary.imitation.raft.PeerId;
import org.zachary.imitation.raft.option.RaftMetaStorageOptions;
import org.zachary.imitation.raft.storage.io.ProtoBufFile;
import org.zachary.utils.io.FileUtils;

import java.io.File;
import java.io.IOException;

/**
 * 基于本地文件存储的元数据存储器
 */
public class LocalRaftMetaStorage implements RaftMetaStorage {
	private static final Logger LOG = LoggerFactory.getLogger(LocalRaftMetaStorage.class);

	/** 元数据文件的名称 */
	private static final String RAFT_META = "raft_meta";
	/** 判断元数据是否已经初始化（当前节点启动的时是否已经从本地元数据文件中把之前的元数据加载到内存中），这里的内存指的是 NodeImpl 类中相关的字段 */
	private boolean initialized;
	/** 元数据文件的路径 */
	private final String path;
	/** 当前节点之前的任期 */
	private int term;
	/** 当前节点最后一次给哪个节点投票了 */
	private PeerId votedFor;
	// 当前节点的实现类
	private NodeImpl node;

	public LocalRaftMetaStorage(final String path) {
		this.path = path;
	}

	/** 元数据存储器的初始化方法，这个方法会在 NodeImpl#init 中被调用，相当于节点初始化时，该节点的元数据就已经从本地元数据文件中加载到内存了 */
	@Override
	public boolean init(final RaftMetaStorageOptions opts) {
		// 如果元数据已经初始化了（从磁盘加载到内存），就直接返回
		if (this.initialized) {
			LOG.warn("Raft meta storage has already been initialized.");
			return true;
		}

		// 从元数据存储器的配置参数 RaftMetaStorageOptions 对象中获得当前正在初始化的 NodeImpl 节点
		this.node = opts.getNode();
		try {
			FileUtils.forceMkdir(new File(this.path));
		} catch (final IOException e) {
			LOG.error("Fail to mkdir {}", this.path, e);
			return false;
		}
		// load() 方法用来执行从元数据文件加载到内存的操作，返回加载是否成功
		if (load()) {
			this.initialized = true;
			return true;
		} else {
			return false;
		}
	}

	/** 从本地的元数据文件加载到内存（NodeImpl 类中相关的字段） */
	private boolean load() {
//		//创建一个ProtoBufFile对象，该对象用来加载本地的元数据文件
//		final ProtoBufFile pbFile = newPbFile();
//		try {
//			//加载元数据文件，这里加载完毕之后，会根据Protobuf协议把元数据文件中的数据解析成一个StablePBMeta
//			//元数据文件中的数据都会封装带这个StablePBMeta中
//			final LocalStorageOutter.StablePBMeta meta = pbFile.load();
//			//判空，然后根据meta中的数据对成员变量term和votedFor赋值
//			if (meta != null) {
//				//将当前节点之前的任期赋值
//				this.term = meta.getTerm();
//				//获得当前节点最后一次给哪个节点投过票
//				return this.votedFor.parse(meta.getVotedfor());
//			}
//			//为null直接返回true，由此可见，在这个jraft框架中，程序加载本地元信息文件失败了，也不会报错，而是直接返回true
//			//大不了节点初始化任期就是0，反正可以和其他节点通信，修改自己的任期值
//			return true;
//		} catch (final FileNotFoundException e) {
//			//这里跑出的是找不到文件的异常，就算抛这个异常，也返回true
//			return true;
//		} catch (final IOException e) {
//			//报出其他异常才返回false
//			LOG.error("Fail to load raft meta storage", e);
//			return false;
//		}

		return true;
	}

	//根据元数据文件的本地路径和元数据文件名称创建一个用来加载元数据文件的
	//ProtoBufFile对象
	private ProtoBufFile newPbFile() {
		return new ProtoBufFile(this.path + File.separator + RAFT_META);
	}

	/** 元数据落盘。当程序在执行时，当前节点的任期可能会发生变化，也可能会给其它节点投票，只要出现了这些动作，那么这些数据就要持久化到硬盘中 */
	private boolean save() {
//		//记录持久化开始时间
//		final long start = Utils.monotonicMs();
//		//创建序列化数据，还是通过Protobuf协议进行落盘，所以要创建一个StablePBMeta对象
//		final LocalStorageOutter.StablePBMeta meta = LocalStorageOutter.StablePBMeta.newBuilder()
//				//在这里把当前节点任期穿进去
//				.setTerm(this.term)
//				//为哪个节点投票也传进去
//				.setVotedfor(this.votedFor.toString())
//				.build();
//		//根据数据要存放的本地文件的路径和文件名获得ProtoBufFile对象
//		final ProtoBufFile pbFile = newPbFile();
//		try {//开始持久化操作
//			if (!pbFile.save(meta, this.raftOptions.isSyncMeta())) {
//				reportIOError();
//				return false;
//			}
//			return true;
//		} catch (final Exception e) {
//			LOG.error("Fail to save raft meta", e);
//			reportIOError();
//			return false;
//		} finally {//执行完毕后获得该操作耗费的时间
//			final long cost = Utils.monotonicMs() - start;
//			if (this.nodeMetrics != null) {
//				this.nodeMetrics.recordLatency("save-raft-meta", cost);
//			}//记录日志
//			LOG.info("Save raft meta, path={}, term={}, votedFor={}, cost time={} ms", this.path, this.term,
//					this.votedFor, cost);
//		}

		return false;
	}

//	private void reportIOError() {
//		this.node.onError(new RaftException(EnumOutter.ErrorType.ERROR_TYPE_META, RaftError.EIO,
//				"Fail to save raft meta, path=%s", this.path));
//	}

	@Override
	public void shutdown() {
		if (!this.initialized) {
			return;
		}
		save();
		this.initialized = false;
	}

	private void checkState() {
		if (!this.initialized) {
			throw new IllegalStateException("LocalRaftMetaStorage has not been initialized");
		}
	}

	@Override
	public boolean setTerm(final int term) {
		checkState();
		this.term = term;
		return save();
	}

	@Override
	public int getTerm() {
		checkState();
		return this.term;
	}

	@Override
	public boolean setVotedFor(final PeerId peerId) {
		checkState();
		this.votedFor = peerId;
		return save();
	}

	@Override
	public PeerId getVotedFor() {
		checkState();
		return this.votedFor;
	}

	@Override
	public boolean setTermAndVotedFor(final int term, final PeerId peerId) {
		checkState();
		this.votedFor = peerId;
		this.term = term;
		return save();
	}

	@Override
	public String toString() {
		return "RaftMetaStorageImpl [path=" + this.path + ", term=" + this.term + ", votedFor=" + this.votedFor + "]";
	}
}
